﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using AbstractProducts;
using log4net;

namespace SmartAssembly
{
    /// <summary>
    /// The {SA} database needs to be configured in the following way:
    /// 
    /// 1) ALTER TABLE dbo.ExceptionReports ADD Processed BIT NOT NULL DEFAULT 0, BugTrackerKey NVARCHAR(512) NULL, IsInternalReport BIT NOT NULL DEFAULT 0
    /// 2) CREATE NONCLUSTERED INDEX ExceptionReportsProcessedIndex ON dbo.ExceptionReports(Processed ASC, CreationDate ASC)
    /// </summary>
    public sealed class ReportRepository : IReportRepository
    {
        private static readonly ILog s_Log = LogManager.GetLogger("SmartAssembly.ReportRepository");

        private sealed class Database : SDK.Database
        { }


        public void DownloadNewReports()
        {
            var processStartInfo =
                new ProcessStartInfo(Path.Combine(SDK.Helpers.GetSmartAssemblyPath(), "SmartAssembly.com"),
                                     "/downloadnewreports")
                {
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,

                };

            Process downloadReportsProcess = new Process { StartInfo = processStartInfo };

            downloadReportsProcess.Start();

            while (!downloadReportsProcess.HasExited)
            {
                s_Log.Info(downloadReportsProcess.StandardOutput.ReadLine());
                Thread.Sleep(50);
            }

            downloadReportsProcess.WaitForExit();
        }

        public IEnumerable<Report> GetUnprocessedReports(ProductRef productRef, DateTime earliestBuildDate)
        {
            DateTime safeEarliestBuildDate = earliestBuildDate < (DateTime) (SqlDateTime.MinValue)
                                                 ? (DateTime) (SqlDateTime.MinValue)
                                                 : earliestBuildDate;

            IList<Guid> ids = new List<Guid>();
            using (Database database = new Database())
            {
                using (DbDataReader reader = database.ExecuteReader(@"
SELECT ExceptionReports.ID
FROM    dbo.ExceptionReports
INNER JOIN dbo.Projects ON dbo.ExceptionReports.ProjectID = dbo.Projects.ID
INNER JOIN dbo.Builds ON dbo.ExceptionReports.AssemblyID = dbo.Builds.AssemblyID
WHERE   Processed = 0
AND     Name = @1
AND     BuildDate >= @2
ORDER BY CreationDate ASC
", productRef.ProductName, safeEarliestBuildDate))
                {
                    while (reader.Read())
                    {
                        Guid id = reader.GetGuid(reader.GetOrdinal("ID"));
                        ids.Add(id);
                    }
                }
            }

            int n = 1;
            foreach (Guid id in ids)
            {
                s_Log.InfoFormat("Getting data for report {0} of {1} from {2}", n++, ids.Count, productRef.ProductName);

                XDocument data;
                using (Database database = new Database())
                {
                    using (DbDataReader reader = database.ExecuteReader(@"
SELECT  Data
FROM    dbo.ExceptionReports
WHERE   ID = @1
", id))
                    {
                        if (!reader.Read())
                        {
                            throw new InvalidOperationException("Report no longer exists");
                        }

                        data = GetData(reader);
                    }
                }

                Report report = null;
                try
                {
                    report = new Report(id, productRef, data);
                }
                catch (Exception e)
                {
                    s_Log.Warn(String.Format("Exception reading report with id {0}", id), e);
                    MarkAsProcessed(id, String.Empty, false);
                }
                if (report != null)
                {
                    yield return report;
                }
            }
        }

        private static XDocument GetData(IDataRecord record)
        {
            int ordinal = record.GetOrdinal("Data");
            int length = (int)record.GetBytes(ordinal, 0, null, 0, 0);
            byte[] data = new byte[length];
            record.GetBytes(ordinal, 0, data, 0, length);
            byte[] unzippedData = SDK.Helpers.Unzip(data);
            using (Stream stream = new MemoryStream(unzippedData, false))
            {
                using (XmlTextReader xmlTextReader = new XmlTextReader(stream))
                {
                    XDocument document = XDocument.Load(xmlTextReader);
                    return document;
                }
            }
        }

        public void MarkAsProcessed(Report report, string key, bool isInternalReport)
        {
            MarkAsProcessed(report.Id, key, isInternalReport);
        }

        private static void MarkAsProcessed(Guid id, string key, bool isInternalReport)
        {
            using (Database database = new Database())
            {
                database.ExecuteNonQuery(@"UPDATE dbo.ExceptionReports SET Processed = 1, BugTrackerKey = @2, IsInternalReport = @3 WHERE Id = @1", id, key, isInternalReport ? "1" : "0");
            }
        }
    }
}
